import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import java.awt.BasicStroke;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;


import javax.swing.BoxLayout;
import java.awt.Color;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

/**
 * The Class HexUI acts as the View component of the application.
 * 
 * @author Jedd Haberstro (joh9446@rit.edu)
 */
public class HexUI extends JFrame implements ModelListener
{

	private JPanel contentPane;
	private JPanel boardPanel;
	private JLabel colorLabel;
	private Panel buttonsPanel;
	private ViewListener viewListener;
	private ColorType color = ColorType.NONE;
	private BoardNode[][] nodes = new BoardNode[HexModel.BOARDWIDTH][HexModel.BOARDHEIGHT];
	private ColorType[][] placedPieces = new ColorType[HexModel.BOARDWIDTH][HexModel.BOARDHEIGHT];
	private boolean myTurn = false;

	/**
	 * The class BoardNode represents a circular area on screen where a player can play a game piece. 
	 * 
	 * @author jhaberstro
	 */
	private class BoardNode {
		public int x, y, radius;
		
		/**
		 * Construct a new BoardNode
		 * @param x
		 * @param y
		 * @param r
		 */
		public BoardNode(int x, int y, int r) {
			this.x = x; this.y = y; this.radius = r;
		}
		
		/**
		 * Checks whether a given point intersects the circular area of this board node.
		 * @param p - the point to test against
		 * @return true upon intersection, false otherwise.
		 */
		public boolean intersects(Point p) {
			return Math.abs(p.x - x) <= radius && Math.abs(p.y - y) <= radius;
		}
		
		/**
		 * Get a string representation of the BoardNode.
		 * @return the string representation.
		 */
		public String toString() { return "BoardNode(" + x + ", " + y + ", " + radius + ")"; }
	}
	
	/**
	 * Create the frame/UI.
	 */
	public HexUI() {
		for (int j = 0; j < HexModel.BOARDHEIGHT; ++j) {
			for (int i = 0; i < HexModel.BOARDWIDTH; ++i) {
				placedPieces[i][j] = ColorType.NONE;
			}
		}
		
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, (int)(650 * (1.5)), 650); // width should always be a 725.0/776.0 percentage of the height.
															  // this keeps the boardPanel perfectly square, and thus makes it so
															  // we can ignore the aspect ratio
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.PAGE_AXIS));
		
		boardPanel = new JPanel() {
			/**
			 * Draw the actual board.
			 */
			@Override
			public void paintComponent(Graphics g) {
				Graphics2D g2 = (Graphics2D) g;	
				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g2.setColor(Color.DARK_GRAY);
				g2.fillRect(0, 0, this.getWidth(), this.getHeight());
				g2.setColor(Color.BLACK);
				
				final int NODE_RADIUS = 10;
				final int NUM_ROWS = HexModel.BOARDWIDTH + HexModel.BOARDHEIGHT;
				int heightIncrement = (int)(this.getHeight() / (double)NUM_ROWS);
				int widthIncrement = (int)(this.getWidth() / (double)NUM_ROWS);
				int startX = (this.getWidth() / 2);
				int startY = 30;
				int nodex = HexModel.BOARDWIDTH-1;
				int nodey = HexModel.BOARDHEIGHT-1;
				for (int i = 0; i < NUM_ROWS; ++i) {
					int yspan = i * heightIncrement;
					int[] xs = getRowXs(i, NUM_ROWS, startX, widthIncrement);
					for (int j = 0, nx = nodex, ny = nodey; j < xs.length; ++j, ++nx, --ny) {
						
						// offset to go into middle of circle
						yspan += NODE_RADIUS;
						xs[j] += NODE_RADIUS; 
						nodes[nx][ny] = new BoardNode(xs[j], startY + yspan, NODE_RADIUS);
						
						final float BORDER_STROKE_WIDTH = 7.5f;
						// draw the lines between the board nodes
						if (!(i >= (NUM_ROWS / 2)-1 && j == 0)) {
							if (j == 0 || (j == xs.length - 1 && i >= (NUM_ROWS / 2)-1)) {
								g2.setColor(Color.WHITE);
								g2.setStroke(new BasicStroke(BORDER_STROKE_WIDTH));
							}
							g2.drawLine(xs[j], startY + yspan, xs[j] - (widthIncrement), startY + yspan + heightIncrement);
							g2.setColor(Color.BLACK);
							g2.setStroke(new BasicStroke());
						}
						if (!(i >= (NUM_ROWS / 2)-1 && j == xs.length-1)) {
							if (j == xs.length - 1 || (j == 0 && i >= (NUM_ROWS / 2)-1))
								g2.setStroke(new BasicStroke(BORDER_STROKE_WIDTH));
							g2.drawLine(xs[j], startY + yspan, xs[j] + (widthIncrement), startY + yspan + heightIncrement);
							g2.setStroke(new BasicStroke());
						}
						if (!(i >= (NUM_ROWS / 2)-1 && (j == xs.length-1 || j == 0)))
							g2.drawLine(xs[j], startY + yspan, xs[j], startY + yspan + 2*heightIncrement);
						yspan -= NODE_RADIUS;
						
						xs[j] -= NODE_RADIUS;
						// draw the board node
						ColorType existingColor = placedPieces[nx][ny];
						if (existingColor == ColorType.NONE) {
							g2.fillOval(xs[j], startY + yspan, NODE_RADIUS*2, NODE_RADIUS*2);
						}
						else if (existingColor == ColorType.WHITE) {
							g2.setColor(Color.WHITE);
							g2.fillOval(xs[j] - NODE_RADIUS, startY + yspan - NODE_RADIUS, NODE_RADIUS*4, NODE_RADIUS*4);
						}
						else {
							g2.setColor(Color.BLACK);
							g2.fillOval(xs[j] - NODE_RADIUS, startY + yspan - NODE_RADIUS, NODE_RADIUS*4, NODE_RADIUS*4);
						}
						g2.setColor(Color.BLACK);
					}
					
					if (i < (NUM_ROWS / 2)-1) {
						nodex -= 1;
					}
					else {
						nodey -= 1;
					}
				}
			}
			
			/**
			 * Get the x locations for a given horizontal row across the board.
			 * @param row - index for which row
			 * @param numRows - how many rows in total there are
			 * @param startX - the starting position of the row
			 * @param xIncrement - how much space is between board nodes.
			 * @return an array of x positions.
			 */
			private int[] getRowXs(int row, int numRows, int startX, int xIncrement) {
				final int numRowPoints = row < (numRows / 2) ? row + 1 : numRows - (row + 1);
				int[] points = new int[numRowPoints];
				int xspan = (numRowPoints - 1) * xIncrement;
				int x = startX - xspan;
				for (int j = 0; j < numRowPoints; ++j, x += (xIncrement * 2)) {
					points[j] = x;
				}
				return points;
			}
		};
		boardPanel.addMouseListener(new MouseListener() {

			/**
			 * Check to see if the user has clicked a board node, and if it's their turn then inform the view listener.
			 */
			@Override
			public void mouseClicked(MouseEvent arg0) {
				for (int i = 0; i < nodes.length; ++i) {
					for (int j = 0; j < nodes[i].length; ++j) {
						if (nodes[i][j].intersects(arg0.getPoint())) {
							if (myTurn && placedPieces[i][j] == ColorType.NONE && viewListener != null) {
								viewListener.placePiece(i, j, color);
							}
						}
					}
				}
			}

			@Override
			public void mouseEntered(MouseEvent arg0) { }
			@Override
			public void mouseExited(MouseEvent arg0) { }
			@Override
			public void mousePressed(MouseEvent arg0) { }
			@Override
			public void mouseReleased(MouseEvent arg0) { }
			
		});
		contentPane.add(boardPanel);
		
		colorLabel = new JLabel("You have not yet been assigned a color.");
		colorLabel.setFont(new Font("Serif", Font.BOLD, 18));
		Box box = Box.createHorizontalBox();
		box.setBackground(Color.BLACK);
		box.add(colorLabel);
		contentPane.add(box, BorderLayout.PAGE_END);
	}

	/**
	 * Set the view listener.
	 * @param vl - the new view listener.
	 */
	public void setViewListener(ViewListener vl) {
		viewListener = vl;
	}

	/**
	 * Informs the UI that a join to a game session was successful.
	 * @param c - the color assigned to the player of this client.
	 */
	@Override
	public void joinSuccessful(ColorType c) {
		color = c;
		boolean white = c == ColorType.WHITE;
		myTurn = !white;
		colorLabel.setText(white ? "Your color is white." : "Your color is black.");
	}

	/**
	 * Informs the UI that a join failed.
	 */
	@Override
	public void joinFailed() {
		// TODO Auto-generated method stub
		JOptionPane.showMessageDialog(this, "Unable to join game lobby. Quitting...");
		System.exit(1);
	}

	/**
	 * Informs the UI that a piece has been placed and confirmed and should now be reflected in the view.
	 * @param x - the x position into the grid of piece locations.
	 * @param y - the y position into the grid of piece locations.
	 * @param c - the color of the placed piece.
	 */
	@Override
	public void placePiece(int x, int y, ColorType c) {
		// TODO - switch boolean that keeps track of whose turn it is.
		placedPieces[x][y] = c;
		myTurn = !myTurn;
		this.repaint();
	}

	/**
	 * Informs the UI that the game is over.
	 * @param go - an enum representing why the game is over.
	 */
	@Override
	public void gameOver(GameOverType go) {
		// TODO - replace the prompts with dialogs
		if (go == GameOverType.DISCONNECTED) {
			JOptionPane.showMessageDialog(this, "The other player was disconnected from the game.\nPress OK to quit.");
			System.exit(0);
		}
		else if (go == GameOverType.LOSE) {
			JOptionPane.showMessageDialog(this, "You lost :-(.\nPress OK to quit.");
			System.exit(0);
		}
		else {
			JOptionPane.showMessageDialog(this, "Congratulations, you won!\nPress OK to quit.");
			System.exit(0);
		}
	}
}
